/*
 * Copyright (c) 2012 Tomas Shestakov. <http://code.google.com/p/jmmo/>
 */

package jmmo.engine.property

import org.scalatest.WordSpec
import org.scalatest.matchers.ShouldMatchers
import org.scalatest.mock.MockitoSugar
import org.mockito.Mockito._
import org.mockito.Matchers._

/**
 * @author Tomas Shestakov
 */
class PropertySpec(prop: Property[Int]) extends WordSpec with ShouldMatchers with MockitoSugar {

  def this() = this(Property(0, Identifier('name ->"test")))

  val non = new PropertySupport[Int] {
    val id = Identifier('name ->"NonProperty")
    def isWritable = false
    def isReadable = false
    def apply() = throw new UnsupportedOperationException
    def update(value: Int) { throw new UnsupportedOperationException }
    def canEqual(that: Any) = true
    def alter = jmmo.util.emptyPartial[Alteration]
  }

  "A Property" should {

    "provide `apply` method," which {

      "returns value of property if Property is readable" in {
        Property("abc").apply() should be ("abc")

        if (prop.isReadable) {
          info("value=" + prop())
        }
      }

      "produce UnsupportedOperationException if Property is not readable" in {
        evaluating {
          non.apply()
        } should produce [UnsupportedOperationException]

        if (!prop.isReadable) {
          val caught = evaluating {
            prop.apply()
          } should produce [UnsupportedOperationException]
          info("read from not readable property cause error=" + caught)
        }
      }
    }

    "provide `update` method," which {

      "sets value of property if Property is writable" in {
        val p = Property("")
        p.update("abc")
        p.apply should be ("abc")

        if (prop.isWritable) {
          prop.update(1)
          prop.apply should be (1)
        }
      }

      "produce UnsupportedOperationException if Property is not writable" in {
        evaluating {
          non.update(1)
        } should produce [UnsupportedOperationException]

        if (!prop.isWritable) {
          val caught = evaluating {
            prop.update(1)
          } should produce [UnsupportedOperationException]
          info("write from not writable property cause error=" + caught)
        }
      }
    }

    "provide `serializedCopy` method which returns SilentObservable with same id, attributes and value (filter is ignored)" in {
      //if (prop.isReadable) {
        val silentValue = prop.serializedCopy()
        silentValue.id should  equal (prop.id)
        silentValue() should equal (prop())
      //}
    }

    "provide appropriate `equals` and `canEqual` methods to check properies equivalence" in {
      Property("abc") should equal (Property("abc"))
      Property(1) should equal (Property(1))
      Property(0.5) should equal (Property(0.5))
      Property(1) should equal (Property(1.0))
      Property(1) should not equal (Property(1.000001))
      Property(1) should not equal (Property("1"))
      Property(1) should not equal (1)
    }

    "provide appropriate `toString` method" in {
      val p = Property("abc")
      info(p.toString)
      p.toString should be("Property(" + p() + "," + p.id + ")")
    }

    {
      val mockListenerStr = mock[ChangeListeners.Listener]
      when(mockListenerStr.isDefinedAt(any())).thenReturn(true)
      val mockListenerInt = mock[ChangeListeners.Listener]
      when(mockListenerInt.isDefinedAt(any())).thenReturn(true)
      val p = Property("")
     
      "provide `addChangeListener` method to add listener of Property change" in {
        p.addChangeListener(mockListenerStr)
        p() = "abc"
        verify(mockListenerStr).apply(ChangePropertyEvent(p, "", "abc"))
//        verifyNoMoreInteractions(mockListenerStr)
  
        if (prop.isWritable) {
          prop() = 0
          prop.addChangeListener(mockListenerInt)
          prop() = 1
          verify(mockListenerInt).apply(ChangePropertyEvent(prop, 0, 1))
//          verifyNoMoreInteractions(mockListenerInt)
        }
      }
      
      "provide `removeChangeListener` method to remove listener of Property change" in {
        p.removeChangeListener(mockListenerStr)
        p() = "123"
        verify(mockListenerStr, never()).apply(ChangePropertyEvent(p, "abc", "123"))
//        verifyNoMoreInteractions(mockListenerStr)

        if (prop.isWritable) {
          prop.removeChangeListener(mockListenerInt)
          prop() = 2
          verify(mockListenerInt, never()).apply(ChangePropertyEvent(prop, 1, 2))
//          verifyNoMoreInteractions(mockListenerInt)
        }
      }
    }
  }
}
